package coderead.plugin;
/**
 * @Copyright 源码阅读网 http://coderead.cn
 */

import coderead.plugin.bean.ArtifactClass;
import coderead.plugin.bean.SimpleArtifactInfo;
import coderead.plugin.bean.SimpleSearchResult;
import coderead.plugin.common.UtilJson;
import com.fasterxml.jackson.core.type.TypeReference;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import javax.net.ssl.SSLException;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author 鲁班大叔
 * @date 2021
 */
public class PluginContext {
    private static final Properties configs;
    private static Logger LOG;
    private static final OkHttpClient okHttpClient;
    private static final String DEFAULT_REMOTE_URL = "https://mvn.coderead.cn/api";
    static final String maven_Template =
            "<dependency>\n" +
                    "   <groupId>%s</groupId>\n" +
                    "   <artifactId>%s</artifactId>\n" +
                    "   <version>%s</version>\n" +
                    "</dependency>";

    static final String gradle_groovy_long_template = "implementation group: '%s', name: '%s', version: '%s'";
    static final String gradle_groovy_short_template = "implementation '%s:%s:%s'";
    static final String gradle_kotlin_template = "implementation(\"%s:%s:%s\")";
    private static String token;
    private static final String appVersion;

    static {
        try {
            LOG = Logger.getInstance(PluginContext.class);
        } catch (Exception ignored) {
        }
        configs = new Properties();
        try {
            configs.load(PluginContext.class.getResourceAsStream("/coderead-maven-plugin.properties"));
        } catch (IOException e) {
            throw new RuntimeException();
        }
        String cacheFile = System.getProperty("user.home") + "/.m2/repository/maven-search/cache";
        File cachedDirectory = new File(cacheFile);
        if (!cachedDirectory.exists()) {
            cachedDirectory.mkdirs();
        }

        appVersion = configs.getProperty("version");
        okHttpClient = new OkHttpClient()
                .newBuilder()
                .cache(new Cache(cachedDirectory, 1000))
                .callTimeout(10, TimeUnit.SECONDS)
                // 信用所有证书
                .hostnameVerifier((s, sslSession) -> true)
                .build();

        try {
            token = buildToken();
        } catch (IOException e) {
            LOG.warn(e);
            token = UUID.randomUUID().toString().replaceAll("-", "");
        }
    }

    private static String buildToken() throws IOException {
        String separator = File.separator;
        String path = PathManager.getConfigPath() + separator + "mavenSearch.token";
        File file = new File(path);
        if (!file.exists()) {
            file.createNewFile();
            Files.write(file.toPath(), UUID.randomUUID().toString().
                                           replaceAll("-", "").getBytes());
        }
        byte[] bytes = Files.readAllBytes(file.toPath());
        return new String(bytes, StandardCharsets.UTF_8);
    }


    public static String getConfig(String key, String defaultValue) {
        return configs.getProperty(key, defaultValue);
    }

    public static void setConfig(String key, String value) {
        configs.setProperty(key, value);
    }

    public static List<SimpleSearchResult> searchArtifact(String keyword) throws IOException {
        if (keyword.length() > 150) {
            keyword = keyword.substring(0, 150);
            LOG.warn("Artifact搜索字符串过长，已截断至150");
        }
        Future<Response> future = httpGet(configs.getProperty("remote.url", DEFAULT_REMOTE_URL) + "/search", "keyword", keyword);
        Response response = getAndCheckResponse(future);
        String jsonValue = response.body().string();
        List<SimpleSearchResult> simpleSearchResults = UtilJson.readValueToList(jsonValue, new TypeReference<List<SimpleSearchResult>>() {
        });
        return simpleSearchResults;
    }

    public static List<SimpleArtifactInfo> getAllArtifact(String groupId, String artifactId) throws IOException {
        String url = configs.getProperty("remote.url", DEFAULT_REMOTE_URL) + "/version";
        Future<Response> future = httpGet(url, "groupId", groupId, "artifactId", artifactId);
        Response response = getAndCheckResponse(future);
        String jsonValue = response.body().string();
        return UtilJson.readValueToList(jsonValue,
                new TypeReference<List<SimpleArtifactInfo>>() {
                });
    }

    public static List<ArtifactClass> searchClass(String name) throws IOException {
        if (name.length() > 200) {
            name = name.substring(0, 200);
            LOG.warn("class 搜索字符串过长，已截断至200");
        }

        Future<Response> future = httpGet(configs.getProperty("remote.url", DEFAULT_REMOTE_URL) + "/search/class", "keyword", name);
        int timeout = Integer.parseInt(configs.getProperty("remote.timeout", "5000"));
        Response response = getAndCheckResponse(future);
        String jsonValue = response.body().string();
        List<ArtifactClass> simpleSearchResults =
                UtilJson.readValueToList(jsonValue, new TypeReference<List<ArtifactClass>>() {
                });
        return simpleSearchResults;
    }

    public static Future<Response> httpGet(String url, String... params) {
        assert params.length % 2 == 0;
        StringBuilder paramsBuilder = new StringBuilder("token=" + token + "&appVersion=" + appVersion);
        for (int i = 0; i < params.length; i++) {
            paramsBuilder.append("&");
            paramsBuilder.append(params[i]);
            paramsBuilder.append("=");
            try {
                paramsBuilder.append(URLEncoder.encode(params[++i], "UTF-8"));
            } catch (UnsupportedEncodingException ignored) {
            }
        }
        final CompletableFuture asyncPromise = new <Response>CompletableFuture();
        final Request request = new Request.Builder()
                .cacheControl(new CacheControl.Builder()
                        .maxAge(7, TimeUnit.DAYS)
                        .build())
                .url(url + "?" + paramsBuilder)
                .build();
        final Call call = okHttpClient.newCall(request);
        // 异步执行
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                asyncPromise.completeExceptionally(e);
                if (e instanceof SSLException) {
                    LOG.error("ssl异常，可以尝试在：设置/tools/Maven search/Remote Url 将https改成http");
                } else {
                    LOG.error(String.format("请求失败：%s", request.url() + "。如未解决，请反馈给我微信：z276386551"), e);
                }
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                asyncPromise.complete(response);
            }
        });
        return asyncPromise;
    }

    public static Future<Response> httpPost(String url, String... params) {
        assert params.length % 2 == 0;
        FormBody.Builder builder = new FormBody.Builder();
        for (int i = 0; i < params.length; i++) {
            builder.add(params[i++], params[i]);
        }
        final Request request = new Request.Builder()
                .cacheControl(new CacheControl.Builder()
                        .noStore()
                        .build())
                .post(builder.build())
                .url(url + "?token=" + token + "&appVersion=" + appVersion)
                .build();
        final Call call = okHttpClient.newCall(request);
        final CompletableFuture asyncPromise = new <Response>CompletableFuture();
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                LOG.error("POST请求失败", e);
                asyncPromise.completeExceptionally(e);
            }

            @Override
            public void onResponse(Call call, Response response) {
                asyncPromise.complete(response);
            }
        });
        return asyncPromise;
    }

    private static Response getAndCheckResponse(Future<Response> future) throws IOException {
        int timeout = Integer.parseInt(configs.getProperty("remote.timeout", "10000"));
        // 异步转同步
        Response response;
        try {
            response = future.get(timeout, TimeUnit.MILLISECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            throw new IOExceptionWithCause(e);
        }

        if (response.code() >= 400) {
            String error = response.body().string();
            HttpUrl url = response.request().url();
            throw new IllegalStateException(String.format("%s 请求失败:%s", url, error));
        }
        return response;
    }


    public static void count(SimpleArtifactInfo info) {
        String url = configs.getProperty("remote.url", DEFAULT_REMOTE_URL) + "/count/version";
        httpPost(url, "groupId", info.groupId, "artifactId", info.artifactId, "versionId", info.version);
    }

    public static String buildMavenText(SimpleArtifactInfo info) {
        return String.format(maven_Template, info.groupId, info.artifactId, info.version);
    }

    public static String buildGradleKotlinText(SimpleArtifactInfo info) {
        return String.format(gradle_kotlin_template, info.groupId, info.artifactId, info.version);
    }

    public static String buildGroovyLongText(SimpleArtifactInfo info) {
        return String.format(gradle_groovy_long_template, info.groupId, info.artifactId, info.version);
    }

    public static String buildGroovyShortText(SimpleArtifactInfo info) {
        return String.format(gradle_groovy_short_template, info.groupId, info.artifactId, info.version);
    }

    public static class IOExceptionWithCause extends IOException {
        private static final long serialVersionUID = 1L;

        public IOExceptionWithCause(String message, Throwable cause) {
            super(message, cause);
        }

        public IOExceptionWithCause(Throwable cause) {
            super(cause);
        }
    }

}
